package edu.vt.ece.rt.benchmark.bank;

import java.util.HashMap;
import java.util.Random;

import edu.vt.ece.rt.dstm.QObject;
import edu.vt.ece.rt.quorum.ClustersManager;
import edu.vt.ece.rt.quorum.Resource;
import edu.vt.ece.rt.quorum.util.Names;
import edu.vt.ece.rt.util.Logger.Logger;



public class Benchmark extends edu.vt.ece.rt.benchmark.Benchmark{

	final int amount = 10000;
	final int transfer = 10;
	private Random random = new Random(hashCode());
	
	
	@Override
	protected void createLocalObjects() {
		Integer id = ClustersManager.baseClusterId;

		/* stores the total value in all accounts */
		BankAccount b = new BankAccount(id+"-global", localObjectsCount * amount);

		for(int i=0; i < localObjectsCount; i++) {
//				Logger.debug("Created quorum object " + i);
				try {
					new BankAccount(id + "-" + i, amount);
				} catch (Throwable e) {
					e.printStackTrace();
				}
		}
		
		
		
		//Add some wait to make sure objects are created successfully
			
//			while (Resource.objectsRegistered !=  (localObjectsCount)*(Integer.getInteger(Names.nodes)-1)) {
//		    	 try {
//						Thread.sleep(20000L);	
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//				
//			}
//			System.out.println("Object wait over for "+id);
		try {
			synchronized(Resource.waitObj) {
				while (Resource.objectsRegistered !=  (localObjectsCount + 1)*(Integer.getInteger(Names.nodes)-1)) {
//					Thread.sleep(20000L);
					Resource.waitObj.wait();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("Resume benchmark for " + id);
	}

	@Override
	protected int getOperandsCount() {	return 2; }

	@Override
	protected Object randomId() {
		int obj = random.nextInt(localObjectsCount);
		return (obj%ClustersManager.nodesCount()) + "-" + obj ;
	}

	@Override

	protected void readOperation(Object... ids) {
		try {
//			System.out.println("Read op starting " +ClustersManager.baseClusterId);

			Random r = new Random();
			int nodeId1 = r.nextInt(ClustersManager.nodesCount());
			int obj1 = r.nextInt(localObjectsCount);
			String acc1 = nodeId1+"-"+obj1;
//			System.out.println("Read op started  on "+ obj1 + " "+ClustersManager.baseClusterId);
			BankAccount.totalBalanceNested(acc1, null);
//			System.out.println("Read op over for "+ClustersManager.baseClusterId);
		} catch (Throwable e) {
			e.printStackTrace();
		} 
	}

	@Override
	protected void writeOperation(Object... ids) {
		try {
			Random r = new Random();
			int nodeId1 = r.nextInt(ClustersManager.nodesCount());
			int obj1 = r.nextInt(localObjectsCount);
			
			int nodeId2 = r.nextInt(ClustersManager.nodesCount());
			int obj2 = r.nextInt(localObjectsCount);
			while(obj2 == obj1) 
				obj2 = r.nextInt(localObjectsCount);
			String acc1 = nodeId1+"-"+obj1;
			String acc2 = nodeId2+"-"+obj2;
			
//			BankAccount.rootTransaction2(acc1, acc2);
			BankAccount.flatNestedTransfer(acc1, acc2);
//			System.out.println("Write op over for "+ClustersManager.baseClusterId);
		} catch (Throwable e) {
			e.printStackTrace();
		} 
	}

	@Override
	protected String getLabel() {
		return "Bank-QR";
	}


	@Override
	protected void checkSanity() {
//		if (ClustersManager.baseClusterId != 0) {
//			try {
//				Thread.sleep(5000);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//			return;
//		}
//		try {
//			Logger.debug("Going to Sleep before Sanity Check");
//			Thread.sleep(5000);
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
		HashMap<String, Long> map = new HashMap<String, Long>();
		long balance = 0;
		int node = Integer.getInteger(Names.nodes);
		for(int i=0; i<node; i++){
			for(int j=0;j<localObjectsCount;j++){
					try {
//						long b=((BankAccount) Resource.getQObject(j + "-" + i)).checkBalance();
						String id = i+"-"+j;
						long b= BankAccount.getBalance(id, null);
//						long b= ((BankAccount)Resource.getQObject(id)).checkBalance();
						map.put(id, new Long(b));
//						Logger.debug(id+ " amount " + b); 
						balance +=b;
					} catch (Throwable e) {
						e.printStackTrace();
					}	
			}
		}

		int globalSum  = 0;
		for(int i=0; i<node; i++){
			try {
				String id = i+"-global";
				long b= BankAccount.getBalance(id, null);
				Logger.debug(id+ " amount " + b); 
				globalSum +=b;
			} catch (Throwable e) {
				e.printStackTrace();
			}	
		}

		if(globalSum == balance && balance==node*localObjectsCount*amount){
			System.out.println("Passed sanity check, balance for "+ClustersManager.baseClusterId+" is "+balance);
			
			Logger.debug("Passed sanity check, balance for "+ClustersManager.baseClusterId+" is "+balance);
			Logger.debug("Passed sanity check, global balance for "+ClustersManager.baseClusterId+" is "+globalSum);
//			for (String id : map.keySet()) {
//				Logger.debug("Account "+id +" has "+map.get(id));
//			}
		}
		else{
			System.out.println("Failed sanity check for "+ClustersManager.baseClusterId+" "+
					"\nbalance = "+ balance+
					"\nexpected = "+localObjectsCount*amount);
			Logger.debug("Failed sanity check for "+ClustersManager.baseClusterId+" "+
					"\nbalance = "+ balance+
					"\nexpected = "+localObjectsCount*amount);
			Logger.debug("Passed sanity check, global balance for "+ClustersManager.baseClusterId+" is "+globalSum);

		}
	}
}
